#include <fat16.h>
#include <hdd.h>
#include <string.h>
#include <vga.h>
#include <i386.h>
#include <debug.h>

struct str_fat16_chardev {
	chardev cd;
	fat16 *file_system;
	fat16_direntry file_entry;
	unsigned int file_pointer;
 	unsigned int flags;
} __attribute__((packed));

typedef struct str_fat16_chardev fat16_chardev;

fat16_chardev fat16_files_open[MAX_OPEN_FILE];

int str_to_fat_name(const char * name, char * fat_name) {
	
	int p, i;
	
	if (strlen(name) > 12 || strlen(name) == 0) return 1;
	
	for (i = 0; i < 11; i++) fat_name[i] = ' ';
	
	for (p = 0; p < strlen(name) && name[p] != '.' ; p++);	
	
	if (p == 0 || p > 7) return 1;
	
	for (i = 0; i < p; i++) fat_name[i] = toUpper(name[i]);
	
	if (p != strlen(name))
		for (i = p + 1; i < strlen(name); i++) fat_name[8 + i - p - 1] = toUpper(name[i]);
	
	return 0;
}

//esta funcion toma un nombre de archivo y lo busca en el directorio de la fat (por ahora sin soporte para directorios)
fat16_direntry * fat_get_file_entry(fat16 *this,  const char * filename) {
	
	char sector[512];
	char fat_name[11];
	int i, next_sector;
	fat16_direntry *entry;
	int entradas_por_sector = 512 / sizeof(fat16_direntry);
	blockdev* dev = this->dev;
	
	//temporal para sacar la '/'
	filename++;
	
	next_sector = this->inicio_del_directorio;
	
	if (str_to_fat_name(filename, fat_name)) 
		return NULL;
	
	for (i = 0; i < this->entradas_del_directorio; i++) {
		
		if (i % entradas_por_sector == 0) {
			dev->read(dev, next_sector, sector, 1);
			next_sector++;
			entry = (fat16_direntry *) sector;
		}
		
		if (entry->nombre[0] == 0)
			break;
		
		if (!strncmp(fat_name, entry->nombre, 11))
			return entry;
			
		entry++;
	}
	
	return NULL;
}

uint_16	get_fat_entry(fat16 * fat, unsigned int entry_number) {
	  
	unsigned int target_sector;
	uint_16 *fat_entry;
	blockdev *dev = fat->dev;
	char sector[512];
	
	target_sector = entry_number / (512 / 2) + fat->sectores_reservados;
	
	dev->read(dev, target_sector, sector, 1);
	
	fat_entry = (uint_16 *) sector;
	fat_entry += entry_number % (512 / 2);
	
	return *fat_entry;
}

void fat_load_cluster_sector(fat16 *fat, unsigned int cluster, unsigned int sector, char * buffer) {
	
	unsigned int target_sector;
	blockdev *dev = fat->dev;
	
	target_sector = fat->sectores_reservados + fat->tablas_fat * fat->sectores_por_tabla;
	target_sector += fat->entradas_del_directorio * 32 / fat->bytes_por_sector;
	target_sector += (cluster - 2) * fat->sectores_por_cluster + sector;
	dev->read(dev, target_sector, buffer, 1);
}

sint_32 fat16_read(chardev *this, void * buf, uint_32 size) {
	
	fat16_chardev *cd = (fat16_chardev *) this;
	fat16 *fat = cd->file_system;
	fat16_direntry *file_entry = &(cd->file_entry);
	
	unsigned int internal_cluster, actual_cluster, actual_sector, actual_byte;
	
	uint_16 relative_pointer_cluster = cd->file_pointer;
	char sector[512];
	char *next_byte_to_write;
	unsigned int bytes_leidos = 0;
	
	internal_cluster = cd->file_pointer / fat->bytes_por_cluster;
	actual_cluster = file_entry->cluster;
	
	while (internal_cluster) {
		actual_cluster = get_fat_entry(fat, actual_cluster);
		relative_pointer_cluster -= fat->bytes_por_cluster;
		internal_cluster--;	
	}
	
	actual_sector = relative_pointer_cluster / fat->bytes_por_sector;
	actual_byte = relative_pointer_cluster % fat->bytes_por_sector;
	fat_load_cluster_sector(fat, actual_cluster, actual_sector, sector);
	
	next_byte_to_write = buf;
	while (size && cd->file_pointer < file_entry->size) {
		
		*next_byte_to_write = sector[actual_byte];
		
		next_byte_to_write++;
		actual_byte++;
		size--;
		bytes_leidos++;
		cd->file_pointer++;
		
		if (actual_byte >= fat->bytes_por_sector) {
			actual_sector++;	
			if (actual_sector >= fat->sectores_por_cluster) {
				actual_cluster = get_fat_entry(fat, actual_cluster);
				actual_sector = 0;
			}
			fat_load_cluster_sector(fat, actual_cluster, actual_sector, sector);
			actual_byte = 0;
		}
	}

	return bytes_leidos;
}

sint_32 fat16_seek(chardev *this, uint_32 pos) {
		
	fat16_chardev *cd = (fat16_chardev *) this;
	fat16_direntry *file_entry = &(cd->file_entry);
	
	if (cd->file_pointer + pos >= file_entry->size) {
		pos = file_entry->size - cd->file_pointer;
	}
	
	cd->file_pointer += pos;
	return pos;
}

uint_32 fat16_flush(chardev *this) {

	fat16_chardev *dev;

	dev = (fat16_chardev *) this;

	dev->cd.refcount--;

	//si todavia no hay enlaces al chardev no lo cierro
	if (dev->cd.refcount > 0) 
		return 0;

	//marco el chardev como libre
	dev->cd.clase = FREEDEV;

	return 0;
}

chardev* fat16_open(fat16* this, const char* filename, uint_32 flags) {

	fat16_direntry *file_entry;
	fat16_chardev *fat_dev;

	int i;

	if (!this && !filename) {
		return NULL;
	}

	//busco un chardev libre
	for (i = 0; i < MAX_OPEN_FILE; i++) {
 		if (FREEDEV == fat16_files_open[i].cd.clase) {
			fat_dev = &fat16_files_open[i];
			memset(fat_dev, 0, sizeof(fat16_chardev));
			fat16_files_open[i].cd.clase = CHARDEV;
			break;
		}
	}

	//si no hay ningun chardev libre retorno null
	if (MAX_OPEN_FILE == i) {
		return NULL;
	}

	file_entry = fat_get_file_entry(this, filename);
 
	if (file_entry == NULL) {
		//no existe el archivo
		fat_dev->cd.clase = FREEDEV;
		return NULL;
	} 

	//seteo los campos del chardev
	fat_dev->file_system = this;

	//copio en la estructura la entrada en el directorio de la fat
	memcpy(&fat_dev->file_entry, file_entry, sizeof(fat16_direntry));
	fat_dev->file_pointer = 0;
	fat_dev->flags = flags;

	//seteo las funciones
	fat_dev->cd.flush = fat16_flush;
	fat_dev->cd.read = fat16_read;
	fat_dev->cd.write = NULL;
	fat_dev->cd.seek = fat16_seek;

	fat_dev->cd.refcount = 1;
	
	return (chardev *) fat_dev;
}

//esta funcion toma un dispositivo(disco) y crea un sistema de archivos para el mismo
void fat16_create(fat16* this, blockdev* dev) {

	fat16_BS *fat_boot;
	char sector[512];
	
	//leo el sector 0 del disco
	dev->read(dev, 0, sector, 1);
	fat_boot = (fat16_BS *) sector;
	
	this->bytes_por_sector = fat_boot->bytes_por_sector;
	this->sectores_por_cluster = fat_boot->sectores_por_cluster;
	this->sectores_reservados = fat_boot->sectores_reservados;
	this->tablas_fat = fat_boot->tablas_fat;
	this->entradas_del_directorio = fat_boot->entradas_del_directorio;
	this->sectores_totales = fat_boot->sectores_totales;
	this->sectores_por_tabla = fat_boot->sectores_por_tabla;
	
	this->inicio_del_directorio = this->sectores_por_tabla * this->tablas_fat + this->sectores_reservados;
	this->bytes_por_cluster = this->sectores_por_cluster * this->bytes_por_sector;
	
	this->dev = dev;
	
	
	//Extras
	//~ unsigned int i;
	//~ i = this->bytes_por_sector;
	//~ debug_log("Bytes por sector %d                \n", i);
	//~ 
	//~ i = this->bytes_por_cluster;
	//~ debug_log("Bytes por cluster %d                \n", i);
	//~ 
	//~ i = this->sectores_por_cluster;
	//~ debug_log("sectores por cluster %d                \n", i);
	//~ 
	//~ i = this->sectores_reservados;
	//~ debug_log("Sectores reservados %d                \n", i);
	//~ 
	//~ i = this->tablas_fat;
	//~ debug_log("Cantidad de tablas %d                \n", i);
	//~ 
	//~ i = this->entradas_del_directorio;
	//~ debug_log("entradas del directorio %d                \n", i);
	//~ 
	//~ i = this->sectores_totales;
	//~ debug_log("sectores totales %d                \n", i);
	//~ 
	//~ i = this->sectores_por_tabla;
	//~ debug_log("sectores por tabla %d                \n", i);
	//~ 
	//~ breakpoint();
	
	//~ char nombre[9];
	//~ char extension[4];
	//~ fat16_direntry *entry;
	//~ debug_log("ARCHIVOS EN EL ROOT                               \n");
	//~ dev->read(dev, this->inicio_del_directorio, sector, 1);
	//~ 
	//~ entry = (fat16_direntry *) sector;
	//~ 
	//~ nombre[8] = '\0';
	//~ extension[3] = '\0';
	//~ 
	//~ for (i = 0; i < 5; i++) {
		//~ memcpy(nombre, entry->nombre, 8);
		//~ memcpy(extension, entry->extension, 3);
		//~ i = entry->cluster;
		//~ debug_log("Nombre %s extension %s inicio: %d\n", nombre, extension, i);
		//~ entry++;
	//~ }
	//~ 
	//~ breakpoint();
	
}

void fat16_init(void) {
	//limpio todos los chardevs
	memset(fat16_files_open, 0, MAX_OPEN_FILE * sizeof(fat16_chardev));
}



